# PvLog
# brian wilkerson
# Mu Dynamics [ 4-1-2010 ]
class PvLog

  attr_accessor :name,:heading, :pvs, :filepath
  def initialize(filepath)
    @filepath = filepath
    @name = File.basename(@filepath,".csv")
    parse
  end

  def parse
    @pvs = { "blocked" => [], "missed" => [] }
    @descriptions = Hash.new
    @heading = Hash.new
    @description_heading = ["Exploit","Service","Direction","Published","Severity","CVE","BugTraq"]
    # read the file into an array of rows, each row is an array of cells
    engine_log = File.readlines(@filepath).collect{|x| x.chomp!.split(",")}

    # heading is a hash of headings mapped to column numbers
    engine_log[0].each_index do |h|
      @heading[engine_log[0][h]] = h
    end
    @heading["Status"] = @heading.size

    # create the pvs list with a row for each pv run
    engine_log.each_index do |n|
      row = engine_log[n]
      next unless (row[@heading['Category']] == 'Variant' and row[@heading['Event']] == 'End')
      # Remove the exploit number from the name
      exploit = row[@heading['Exploit']].sub(/\s\(\d+:\d+\)$/,'')
      # status is Missed if the previous row Event is "Fault"
      if engine_log[n-1][@heading['Event']] == "Fault"
        @pvs["missed"] << exploit
      else
        @pvs["blocked"] << exploit
      end
      # add a row to the descriptions array, used when printing comparisons
      description = Array.new
      @description_heading.each do |h|
        description << row[@heading[h]]
      end
      @descriptions[exploit] = description
    end
    # Some pvs have multiple exploits under the same name, remove duplicates
    @pvs["missed"].uniq!
    @pvs["blocked"].uniq!
  end

  # Print 3 lists: AlwaysBlocked, AlwaysMissed, MissedThenDropped
  def compare(compare_to)
    always_blocked = @pvs["blocked"] & compare_to["blocked"]
    print_list "always_blocked", always_blocked

    always_missed = @pvs["missed"] & compare_to["missed"]
    print_list "always_missed", always_missed

    missed_then_dropped = @pvs["missed"] - compare_to["missed"]
    print_list "missed_then_dropped", missed_then_dropped
  end

  def print_list(name,exploits)
    filename = "#{name}.csv"
    list = Array.new
    list << @description_heading.join(",") + "\n"
    exploits.each { |e| list << @descriptions[e].join(",") + "\n" }
    puts "writing: #{filename}"
    File.open(filename,'w'){|f| f.write(list)}
  end
  
  # Print out a template that selects just blocked or missed
  def save_template(status)
    name = "#{@name}_#{status}"
    filename = "#{name}.xml"
    template =  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<mu_config version=\"3.0\">
<templates>
<published_network_attack name=\"#{name}\">
  <analyzer_mode>ClientServer</analyzer_mode>
  <evasion>NONE</evasion>
  <filters>
    <name_filter>
      <operator>IS</operator>
      <values>\n"
    @pvs[status].each do |exploit|
      template += "      <value>#{exploit}</value>\n"
    end
    template += "      </values>
    </name_filter>
  </filters>
</published_network_attack>
</templates>
</mu_config>"
    puts "writting: #{filename}"
    File.open(filename,'w'){|f| f.write(template)}
  end

end

unless ARGV[0] and File.readable?(ARGV[0])
  puts %q[
Usage: pva_template_builder.rb <engine_log.csv> [<compare_to.csv>]

When one .csv is input, two new templates will be created:
engine_log_Missed.xml
engine_log_Blocked.xml

When two .csv are input, three lists are printed:
AlwaysBlocked.csv
AlwaysMissed.csv
MissedThenDropped.csv
  ]
  exit
end

log = ARGV.shift
pv = PvLog.new log
pv.parse

if ARGV[0]
  pv2 = PvLog.new ARGV.shift
  pv.compare(pv2.pvs)
else
  pv.save_template("missed")
  pv.save_template("blocked")
end
